• Friday, September 27, 2024

    In the realm of software engineering, certain principles emerge from experience, often learned the hard way. A recent article highlights four key software design principles that can significantly impact the development process and the reliability of software systems. The first principle emphasizes the importance of maintaining a single source of truth. When data is stored in multiple locations, the risk of inconsistencies increases. For instance, in a frontend application displaying a bank balance, it is advisable to retrieve the balance directly from the server rather than storing it in multiple places. This approach minimizes synchronization issues and ensures that derived values, like a spendable balance, are calculated on-the-fly rather than stored separately. The overarching message is that derived data should be computed rather than duplicated to avoid potential bugs. The second principle challenges the conventional wisdom of "Don't Repeat Yourself" (DRY) by introducing the concept of "Please Repeat Yourself" (PRY). The author argues that striving for excessive reusability can lead to overly complex abstractions that lose their original purpose. Instead of forcing code into a single reusable class, it may be more effective to allow for some code duplication, which can simplify testing and maintenance. This principle acknowledges that while code reuse is valuable, it should not come at the cost of clarity and functionality. The third principle addresses the use of mocks in testing. While mocks can facilitate quick unit tests, they can also lead to issues when the mocked components do not accurately reflect the real dependencies. The author suggests that relying too heavily on mocks can compromise the reliability of tests, as they may not behave as expected in production. Instead, it is recommended to use real dependencies whenever possible, even if it means writing more comprehensive tests. This approach enhances the reliability of the software and reduces the likelihood of encountering issues in production. The final principle focuses on minimizing mutable state. The author argues that while caching and state management are essential in software development, it is crucial to evaluate what data truly needs to be stored versus what can be derived dynamically. By reducing mutable state, developers can avoid synchronization problems and streamline the development process. The principle advocates for a more straightforward approach, allowing for redundant calculations when necessary, as modern computing power can handle such tasks efficiently. These principles serve as valuable guidelines for software engineers, encouraging them to think critically about their design choices and the implications of those choices on the overall reliability and maintainability of their systems. Each principle highlights the importance of simplicity, clarity, and a thoughtful approach to software design, ultimately leading to more robust and effective software solutions.

  • Tuesday, April 23, 2024

    This author built a large-scale service and found certain principles reappearing throughout the implementation. It's useful to prioritize a single source of truth and minimize mutable state when building something from scratch. Developers should also make sure not to abstract things prematurely and not to overuse mocks when writing tests for their code.

  • Thursday, March 7, 2024

    The most fundamental goal of software design is to make code understandable. Understandable code is needed for correctness, security, and performance, as a lack of understanding leads to defects, security issues, and misguided performance optimizations. Focusing on understandability means making the code itself readable and providing supporting documentation to clarify complex concepts or system architecture.

  • Thursday, May 23, 2024

    Software engineers shouldn't be overly perfectionist or blindly follow best practices. They should prioritize continuous refactoring and think hard about choosing the right programming paradigms for the context.

  • Tuesday, April 23, 2024

    Developers often over-engineer and over-design software by abstracting things excessively and focusing on hypothetical future scalability. Instead, it's better to prioritize core functionality first, keep the scope reasonable, and have a “good” solution over a “perfect” one.

  • Wednesday, September 11, 2024

    This developer writes about a few software development habits that have helped him write better software faster. They focus on writing small commits frequently, refactoring continuously, and deploying often. They also often write tests first to define API structures early.

  • Wednesday, August 7, 2024

    The phrase "just implementation details" often underestimates the complexity and difficulty involved in building and deploying software. Designing good software involves challenges like designing a maintainable system, having robustness and observability, and providing a good user experience. The perception that "CRUD" applications are simple is not true since they also require careful database design, production support, and handling of background jobs, user logins, and permissions.

  • Wednesday, June 12, 2024

    There are three fundamental laws of software complexity: systems inevitably degrade in design quality over time, successful systems create complexity through leaky abstractions to gain market share, and there is no upper limit to the complexity a system can reach. Most engineers end up working on poorly designed systems, which only grow more intricate and challenging to work on over time.

  • Monday, March 18, 2024

    Years of encountering slow, unreliable tests and fixing bugs without adding tests have shaken this author’s faith in testing as the ultimate quality indicator. There are many high-quality projects that run surprisingly few tests. The key to software quality isn't the amount of testing, but rather prioritizing tests based on developer judgment and having developers that deeply care about their work.

  • Monday, August 12, 2024

    Data infrastructure projects are often quickly replaced and difficult to maintain. To prevent this, it's important to avoid "resume-driven development," where teams prioritize trendy technologies over practical needs, and the "key person dependency" problem, where only one person has all the knowledge of a system.

  • Monday, March 4, 2024

    Software engineers should propose solutions, not just problems, and focus on team goals over just personal goals. Collaboration trumps code perfection. Software engineers should build relationships for influence, as building trust makes it easier to get buy-in and support for one’s ideas.

  • Monday, August 12, 2024

    Programmers should embrace a mindset of skepticism and constant verification, as trusting abstractions can lead to unexpected problems. Abstractions, while necessary for efficient thinking, are often leaky and can fail in unpredictable ways, so it's important to understand the underlying mechanisms. That's why "trust, but verify” is necessary when working on a project with a lot of abstractions.

    Hi Impact
  • Thursday, May 30, 2024

    Software engineers are destined to wallow in unnecessary complexity due to three fundamental laws: a well-designed system will degrade into a badly designed system over time, complexity is a moat filled by leaky abstractions, and there is no fundamental upper limit on software complexity. Building a new system from scratch without succumbing to these laws is a lot harder than it sounds. Engineers who work on badly designed systems suffer more as badly designed systems have unbound complexity.

  • Monday, September 16, 2024

    Brainstorming at least two distinct designs when creating software allows you to compare and identify the best solution and potentially combine ideas from both. This requires more upfront effort, but prevents costly rework later and improves design quality.

  • Monday, April 8, 2024

    To ship code faster, identify where you spend most of your time: design, coding, testing, review, or deployment. If you’re new to a codebase, focus on understanding code flow, tracing techniques, and learning from experienced colleagues. To optimize testing and review, use feature flags, batch changes, automate testing, and aim for smaller, well-explained code changes for faster reviews.

  • Wednesday, October 2, 2024

    In the realm of frontend development, effective testing is crucial for maintaining high-quality software. Quentin Spencer-Harper, drawing from his extensive experience at Palantir, shares insights gained from managing a significant frontend codebase composed of over a million lines of TypeScript. His reflections highlight the importance of a well-structured testing strategy, emphasizing that the right approach to testing can dramatically enhance engineering velocity. Spencer-Harper identifies three key lessons learned over a decade of experience. First, he underscores that while there are various factors contributing to frontend stability, this discussion will focus specifically on testing. He argues that investing in testing is unique in its potential to double engineering velocity, unlike other improvements that tend to yield only incremental gains. The ability to make changes with confidence allows developers to iterate quickly and refactor code without fear, which is essential for maintaining a healthy codebase. However, he notes a common pitfall: many automated frontend tests can inadvertently slow teams down due to the time required for their creation and maintenance. To counter this, he advocates for a thoughtful testing strategy that minimizes maintenance costs. The maintenance cost of tests is a critical determinant of their long-term value; reducing this cost can exponentially increase the number of tests a team can afford, thereby enhancing overall coverage and effectiveness. Spencer-Harper presents two strategies to manage maintenance costs effectively. The first involves designing tests that are quick to update, allowing developers to swiftly determine whether a change is expected or requires adjustment. The second strategy focuses on testing at the minimal cut, meaning that tests should encompass the smallest necessary scope to ensure stability while avoiding excessive fragility from mocked APIs. He elaborates on the importance of defining the scope of tests carefully. For instance, unit tests should not be overly granular; instead, they should test logical groupings of functionality that reflect the complexity of the application. This approach helps maintain test relevance and reduces the frequency of required updates. Additionally, he advises against writing component tests for React applications, as they often prove to be slow and fragile. Instead, he suggests focusing on testing utility functions that encapsulate complex logic, which can be more stable and easier to maintain. Integration tests should be strategically designed to align with stable APIs, allowing for easy addition of new tests without incurring high maintenance costs. Spencer-Harper cites examples from successful applications, such as MapboxGL, where the testing structure is built around stable API formats, enabling efficient test management. Ultimately, he emphasizes that while traditional testing methods can enhance team performance, they may not suffice to achieve the highest levels of engineering velocity. To truly excel, teams should consider automated solutions that leverage real user interactions to create comprehensive test suites. Meticulous AI is highlighted as a tool that captures user flows and generates a visual snapshot test suite, allowing for near-complete coverage of the codebase without the burden of manual test maintenance. In conclusion, Spencer-Harper's insights advocate for a strategic approach to frontend testing that prioritizes maintenance efficiency and effective coverage. By focusing on the right testing practices and leveraging automation, development teams can enhance their velocity and maintain high-quality software.

  • Wednesday, March 13, 2024

    This essay reflects over a four-decade career in software development. To be a successful software engineer, one must communicate and collaborate well. While "getting things done" is important, developers must also consider the long-term maintenance costs of their work. Readers should find their own best practices for productive software engineering while also remembering to take care of themselves.

    Md Impact
  • Friday, September 27, 2024

    The article discusses the importance of design systems from a developer's perspective, emphasizing how they can enhance usability and streamline the development process. It begins with an analogy comparing the intuitive experience of riding a bicycle to the desired user experience in software applications. Just as riding a bike becomes second nature, the goal is to create software that users can navigate effortlessly. Design systems are presented as a solution to achieve this level of usability, allowing teams to build and ship applications quickly while maintaining consistency through standardized components. The piece highlights the potential pitfalls of poorly implemented design systems, which can hinder rather than help development. It stresses the need to treat design systems as products that require ongoing investment and maintenance to remain effective. A well-maintained design system can provide stability and speed up development, while a neglected one can lead to technical debt and inefficiencies. The article advocates for an iterative approach to building design systems, contrasting it with the traditional waterfall method. Developers are encouraged to focus on simplicity and to build design systems incrementally, using user feedback to guide improvements. The example of Slack illustrates this point, showing how the company managed to standardize its components without sacrificing development speed, ultimately leading to the creation of Slack Kit. Another critical aspect discussed is the necessity of maintaining design systems to prevent them from becoming obsolete. Developers often express skepticism about using design systems due to past experiences with poorly maintained products. The article suggests that design systems should be flexible and adaptable, allowing for both standardization and customization. The Spotify design system, Encore, is cited as an example of balancing consistency with the need for creative freedom. The importance of alignment between developers and designers is also emphasized. Effective communication and collaboration can prevent wasted effort and ensure that both teams are working towards the same goals. The article uses Airbnb's experience to illustrate how a shared visual language can enhance productivity and consistency across teams. Finally, the article encourages viewing design systems as basecamps for exploration rather than constraints on creativity. By providing a stable foundation, design systems can empower developers and designers to innovate and experiment without losing sight of consistency and usability. The piece concludes by promoting UXPin Merge as a tool that facilitates the creation of production-ready prototypes, further enhancing the design and development workflow.

  • Monday, July 8, 2024

    Linus Torvalds emphasizes the importance of data structures over code in software development since good data structures lead to better code design and maintainability. This author supports this view with personal experience, describing how restructuring data in a project allowed the team to move faster in the long run. This prioritization is also how Git grew to be the dominant version control system.

  • Tuesday, June 11, 2024

    Great contributions to a codebase prioritize well-tested features over rushed solutions, have TODOs and feature flags to manage ongoing work, and refactor existing code in-place whenever possible. New changes should be broken down into digestible pull requests. They should also have the same patterns and code style of the existing codebase.

  • Monday, June 24, 2024

    Improving software design to support new features is a process that inevitably involves a temporary decline in performance before reaching a better state. The software designer's role is to envision the desired state and strategically manage the transition process, considering factors like the size of improvement steps, the time to achieve initial value, and the rate of improvement after the initial dip. This process is called "succession" and involves making choices about the design transformation, sequencing steps to minimize disruptions, and balancing risk with efficiency.

    Hi Impact
  • Friday, May 31, 2024

    The DRY (Don't Repeat Yourself) principle can be harmful if applied prematurely. Early abstractions can lead to unnecessary complexity where future requirements may require independent evolution of seemingly similar looking pieces of code. Instead of blindly following DRY, developers should always think through what code sections are truly redundant or may evolve in the future based on business requirements.

  • Friday, May 17, 2024

    Design docs outline the implementation strategy and key design decisions for a software project. They help identify potential issues early on, achieve consensus, and ensure cross-cutting concerns are addressed. They're used extensively at Google for every software project and for documentation and knowledge sharing.

  • Wednesday, May 1, 2024

    Creating software that is “good enough” is much better than trying to create software that is perfect. Software can always be iterated upon after release. HTTP and HTML are examples of imperfect technologies that have staying power thanks to their continuous improvement.

  • Monday, June 3, 2024

    A three-step process for writing clean code: first, prioritize making the code work, then add testing for correctness, and finally, refactor and clean up the code while relying on the tests.

  • Wednesday, June 12, 2024

    Clear, readable code is more valuable than clever code in the long run. This developer's experience at a large tech company revealed that even though clear code might seem trivial, it's actually more difficult to write and maintain. In order to “preserve complexity” for promotion packets, documentation was important for demonstrating the complexity of the work.

  • Monday, June 3, 2024

    Strong types in programming languages like C++ and Rust, which enforce specific data types and rules for variables, lead to fewer bugs, more expressive APIs, and potentially better performance. They prevent invalid input, improve code clarity, and reduce the need for repetitive error checking (like in JavaScript with undefined).

  • Monday, August 19, 2024

    Linus Torvalds (the creator of Git and Linux) says that data structures are more important than code in software development since good data structures lead to better code design and maintainability. This article supports this view with personal experience, describing how restructuring data in a project allowed a team to move faster in the long run. This prioritization is also how Git grew to be the dominant version control system.

  • Tuesday, August 27, 2024

    It's not unusual to find a ratio of 25:1 between developers and ops people in modern software development environments. Giving developers the tools to succeed is more challenging than ever. This ebook, authored by Director of Software Architecture and Distinguished Engineer Christian Oestreich, covers 9 critical practices that enable developers to focus on writing high quality code. These include: Baking ops tasks into project bootstrapping, Building libraries to accelerate code instrumentation, Integrating reporting agents into hosts and containers, Automating platform compliance checks. To see the full list and a detailed explanation of each principle, download the full ebook from Datadog (free).

  • Thursday, April 18, 2024

    Young developers often focus too much on premature optimization, which leads to over-engineering things too early. It's better instead to focus on writing clear, concise, and testable code. This author tells a story where they wrote over-engineered code as a young developer, but when it came time to debug it months later, they could no longer understand the code they had written.